package org.lazycoder.logger;

import java.io.File;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Iterator;
import java.util.Vector;

import org.lazycoder.database.Database;
import org.lazycoder.database.Table;

/**
 * This class is the input interface for the logger. This class is responsible
 * for listening to modules and storing their input to the database.
 * 
 * This class is a singleton as multiple-access to the database would be
 * overhead and less secure. because of this use the
 * {@code EventLogger.getInstance()} to get the current working instance of this
 * object.
 * 
 * @author Matthew Bayliss
 * 
 */
public class EventLogger {
	/** The singleton instance of this class */
	private volatile static EventLogger instance = null;

	/**
	 * Get the singleton instance of this class. This method will also create
	 * the object if it does not yet exist.
	 * 
	 * This is implemented in a thread-safe manner. The actual instantiation of
	 * EventLogger is done in a synchronised block which is only entered into if
	 * the instance variable is null.
	 * 
	 * @return the event logger object.
	 */
	public static EventLogger getInstance() {
		if (instance == null) {
			synchronized (EventLogger.class) {
				if (instance == null) {
					instance = new EventLogger();
				}
			}
		}
		return instance;
	}

	private String path = "";

	private Database db;

	/**
	 * Create an instance of this class and set up the database.
	 */
	private EventLogger() {
	}

	/**
	 * Close the database connection.
	 */
	public synchronized void close() {
		if (db != null) {
			db.stop();
		}
	}

	/**
	 * If the database is open, this method will create the correct tables in
	 * the database.
	 * 
	 * @param file
	 *            Where the DB should be located.
	 */
	public synchronized void create(String file) {
		Database.createDB(file);
		db = new Database(file);
		if (!db.isClosed()) {
			// Check to see if the senders table exists
			System.out.println("DB Open");
			if (!db.tableExists("Senders")) {
				// if it doesn't, create it.
				db.createTable("Senders", new String[] {
						"mid CHAR(64) PRIMARY KEY", "name VARCHAR(100)",
						"description CLOB(250)" });
			}
			// Check to see if the event table exists
			if (!db.tableExists("Creating Events")) {
				// if it doesn't, create it.
				db.createTable(
						"Events",
						new String[] {
								"eid BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1)",
								"sender CHAR(64) CONSTRAINT sender REFERENCES Senders(mid)",
								"time TIMESTAMP", "message CLOB(1k)" });
			}
			// Check to see if the attachment table exists
			if (!db.tableExists("Attachments")) {
				// if it doesn't, create it.
				db.createTable(
						"Attachments",
						new String[] {
								"aid BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1)",
								"eid BIGINT CONSTRAINT eid REFERENCES Events(eid)",
								"name VARCHAR(100)", "att BLOB(200M)" });
			}
		}
		path = file;
	}

	/**
	 * Grab the attachments for a specified event.
	 * 
	 * @param eid
	 *            the key in the event table to grab atts for.
	 * @return A list of files.
	 */
	public synchronized Vector<File> getAttachments(int eid) {
		if (!db.isClosed()) {
			return db.getAttachments("Attachments", "att", "eid=" + eid);
		} else {
			return null;
		}
	}

	/**
	 * get all of the events in the database.
	 * 
	 * @return a list of events
	 */
	public synchronized Table getEvents() {
		if (!db.isClosed()) {
			Table tbl;
			try {
				tbl = new Table(
						db.prepare("SELECT Events.eid, Events.time, Senders.name, Events.Message FROM Events JOIN Senders ON Senders.name = Events.Sender ORDER BY time"));
				return tbl;
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return null;
	}

	/**
	 * @return the location of the database
	 */
	public String getPath() {
		return path;
	}

	/**
	 * @param db
	 *            The database to open
	 * @throws InvalidDatabase
	 *             The database is baaaad.
	 * 
	 */
	public synchronized void open(String db) throws InvalidDatabase {
		Database.createDB(db);
		this.db = new Database(db);
		if (!this.db.tableExists("EVENTS")) {
			this.db.stop();
			throw (new InvalidDatabase());
		}
		if (!this.db.tableExists("SENDERS")) {
			this.db.stop();
			throw (new InvalidDatabase());
		}
		if (!this.db.tableExists("ATTACHMENTS")) {
			this.db.stop();
			throw (new InvalidDatabase());
		}
		path = db;
	}

	/**
	 * Register a new module in the database. This must be done BEFORE trying to
	 * add an event with that module.
	 * 
	 * @param module
	 *            the module to register.
	 */
	public synchronized void register(Module module) {
		String name = module.getName();
		System.out.println("Name: " + name);
		ResultSet result = db.select("SENDERS", new String[] { "mid", "name" },
				"WHERE name='" + name + "'");
		try {
			if (!result.first()) {
				db.insert("SENDERS", new String[] { "mid", "name",
						"description" }, new String[] { name, name,
						"Default Description" });
			} else {
				System.out.println("Module already in database");
			}
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * This is called by modules when they have data to log.
	 * 
	 * @param m
	 *            The module calling EventLogger
	 * @param e
	 *            The Event.
	 */
	public synchronized void update(Module m, Event e) {
		String name = m.getName();
		String ts = new Timestamp(e.getTimestamp().getTime()).toString();
		long id = db.insert("EVENTS", new String[] { "Sender", "Message",
				"Time" }, new String[] { name, e.getMessage(), ts });
		Iterator<File> itr = e.iterator();
		while (itr.hasNext()) {
			File file = itr.next();
			db.insertBlob(
					"Attachments",
					new String[] { "eid", "name", "*att" },
					new String[] { String.valueOf(id), "name",
							file.getAbsolutePath() });
		}

	}

}
